package dataprocess;
/**
 * Paquetes de Java
 */
import java.util.Scanner;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Set;
import java.util.HashSet;

/**
 * Paquetes propios al trabajo
 */
import triviaException.*;
import database.BooleanQuestion;
import database.MultipleQuestion;
import database.NumericQuestion;
import database.Question;
import database.SimpleQuestion;

/**
 * Clase Parser:
 * 	clase que verifica que el archivo sea correcto
 * @author the team
 *
 */
public class Parser{
	FileReader aFile = null;
	Scanner parser = null;
	Integer typeQuestion, difficulty, cantOptions, cantRightOpt;
	String line;
	int easy, medium, hard;
	private static final Integer EASY = 0, MEDIUM = 1, HARD = 2;
	
	public Parser() throws FileNotFoundException {
		super();
		initScanner();
	}

	
	/**
	 * Metodo con el cual abrimos el archivo
	 * @throws FileNotFoundException 
	 */
	private void initScanner() throws FileNotFoundException{	
		aFile =  new FileReader("data/Questions/preguntas.txt");
		parser = new Scanner( aFile);
	}
	
	/** Metodo que parsea el archivo
	 * y extrae las sucesivas preguntas
	 * de la trivia
	 * 
	 * @return Set<Question> 
	 * @throws TriviaException 
	 */
	
	public Set< Question> readFiles() throws TriviaException{
		Set< Question> setQuestions = new HashSet <Question>();
		Question question;
		easy = medium = hard = 1;
		
		while( parser.hasNextLine() ) {
			
			question = readQuestion();
			
			if( setQuestions.add(question) == false ){
				TriviaException e = new TriviaException();
				throw e.repeatException();
		}
		
		
	}
		
		return setQuestions;
}

	/**
	 * Metodo privado con el cual
	 * leemos una pregunta.
	 * Si el archivo esta mal se lanza
	 * una excepcion
	 * @return Question
	 */
	private Question readQuestion() throws TriviaException{
		Question question;
		
		if( !parserTypeQuestion()  ){
			TriviaException e = new TriviaException();
			throw  e.typeQuestionExcepcion();
		}
		
		if( !parserDifficulty()){
			TriviaException e = new TriviaException();
			throw  e.difficultyException();			
		}
		
		if( !parserCantOptions()){
			TriviaException e = new TriviaException();
			throw e.valueException();
		}
		if( !parserCantRightOp() ){
			TriviaException e = new TriviaException();
			throw  e.rightOpException();
		}
				
		line = parser.nextLine();
		
		if( line.equals("") ){
			TriviaException e = new TriviaException();
			throw e.noQuestionException();		
		}
		else{
			question = setTypeQuestion();
		}
		
		if (parser.hasNextLine()) {
			line = parser.nextLine();
		}
			
		
		/* Si el separador no coincide con "####" se comprueba si es
		 * por un final de archivo, en cuyo caso se tiene en cuenta 
		 * que bien podrian ser todas lineas en blanco hasta el final
		 * lo cual no representa ningun tipo de error.
		 */
		
		if( !line.equals("####") ) {
			do {
				if (parser.hasNextLine()) {
					line = parser.nextLine();
				} else {
					line = "";
				}
				
				if (!line.equals("")) {
					TriviaException e = new TriviaException();
					throw e.notSeparatorException();
				}
			} while(parser.hasNextLine());
		}
		
		return question;
	}							
				
	/**
	 * Metodo privado que lee el tipo de 
	 * pregunta devolviendo falso si los valores
	 * no son los especificados en el TPE
	 * @return boolean
	 */
	private boolean parserTypeQuestion(){		
		
		if (parser.hasNextLine())
			line = parser.nextLine();
		else
			return false;
		typeQuestion = Integer.valueOf(line);
		
		if( typeQuestion < 1 && typeQuestion > 5 ){
			return false;
		}
		return true;
	}
	/**
	 * Metodo que lee la dificultad de la
	 * pregunta. Devuelve false si la dificultad
	 * no es 0, 1 o 2
	 * 
	 * @return boolean
	 */
	private boolean parserDifficulty(){
	
		if (parser.hasNextLine())
			line = parser.nextLine();
		else
			return false;
		
		difficulty = Integer.valueOf(line);
	
		if( difficulty.compareTo(EASY) < 0 || difficulty.compareTo(HARD) > 0){
			return false;
		}else if( difficulty.equals(EASY) ){
			easy++;
		}else if( difficulty.equals(MEDIUM)){
			medium++;
		}else{
			hard++;
		}
		return true;
	}
	
	/**
	 * Metodo que lee la cantidad de opciones que
	 * corresponden a la pregunta.
	 * Debe ser un numero positivo
	 * @return boolean
	 */
	private boolean parserCantOptions(){
		
		if (parser.hasNextLine())
			line = parser.nextLine();
		else
			return false;
		
		cantOptions = Integer.valueOf(line);
	
		if( cantOptions < 1){
			return false;
		}
		return true;
		
	}
	/**
	 * Metodo que lee la cantidad de respuestas
	 * correctas. Comprueba que el tipo de pregunta
	 * referenciado por 4 no puede tener una sola
	 * respuesta correcta.
	 * 
	 * @return boolean
	 */
	private boolean parserCantRightOp(){
			
		if (parser.hasNextLine())
			line = parser.nextLine();
		else
			return false;
		
		cantRightOpt = Integer.valueOf(line);
	
		if( typeQuestion != 4 && cantRightOpt != 1 ){
			return false;
		}
		
		return true;
	}

	
	private Question setTypeQuestion() throws TriviaException{
		Question question = null;
		
		switch( typeQuestion){
			case 1:{
				question = new SimpleQuestion( null );
				((SimpleQuestion)question).setQuestionText( line );
				parserSimpleQuestion(question) ;
				break;
			}
			case 2:{
				question = new NumericQuestion( 0.0 );
				((NumericQuestion)question).setQuestionText(line);
				parserNumericQuestion( question );
				break;
			}
			case 3:	
			case 4: {
				question = new MultipleQuestion( null, null);
				((MultipleQuestion)question).setQuestionText(line);
				parserMultipleQuestion( question );
				break;
			}
			case 5:{
				question = new BooleanQuestion( null);
				((BooleanQuestion)question).setQuestionText(line);
				parserBooleanQuestion( question );
				break;
			}
			
		}
		return question;
	}
	
	private void parserSimpleQuestion(Question question ) throws TriviaException{
		String option;
		String answer;
		Integer score;
		
		if (parser.hasNextLine())
			option = parser.nextLine();
		else
			option = "";
		
		if( option.equals("")){
			TriviaException e = new TriviaException();
			throw e.noQuestionException();		
		}
	
		if (parser.hasNextLine())
			answer = parser.nextLine();
		else
			answer = "";
		
		if( !( answer.equals(option))){
			TriviaException e = new TriviaException();
			throw e.invalidAnswerException();
		}
		else{
			((SimpleQuestion)question).setAnswer(answer);
		}
	
		if (parser.hasNextLine()) {
			line = parser.nextLine();
			score = Integer.valueOf(line);
		}
		else
			score = -1;
		
		if( score < 0 ){
			TriviaException e = new TriviaException();
			throw e.valueException();
		}
		else{
			((SimpleQuestion)question).setScore(score);
			((SimpleQuestion)question).setLevel(difficulty);
		}
	
		return;	
	}
	
	private void parserNumericQuestion(Question question ) throws TriviaException{
		Double option;
		Double answer;
		Integer score;
		
		if (parser.hasNextLine()) {
			line = parser.nextLine();
			option = Double.valueOf(line);
		}
		else
			option = 0D;
		
		if (parser.hasNextLine()) {
			line = parser.nextLine();
			answer = Double.valueOf(line);
		}
		else
			answer = Double.NaN;
		
		if( !(option.equals(answer)) ){
			TriviaException e = new TriviaException();
			throw e.invalidAnswerException();
		}
		else{
			((NumericQuestion)question).setAnswer(answer);
		}
		
		if (parser.hasNextLine()) {
			line = parser.nextLine();
			score = Integer.valueOf(line);
		}
		else
			score = -1;
		
		if( score < 0 ){
			TriviaException e = new TriviaException();
			throw e.valueException();
		}
		
		((NumericQuestion)question).setScore(score);
		((NumericQuestion)question).setLevel(difficulty);
					
		return;
	}

	
	private void parserMultipleQuestion(Question question ) throws TriviaException{
		Set< String > options = new HashSet< String>();
		Set< String > answers = new HashSet< String>();
		String[] optaux = null;
		Integer[] correct = null;
		Integer score;
		int correctQtty = 0;
		
		if( !( validate( cantOptions, options))){
			TriviaException e = new TriviaException();
			throw e.repeatException();
		}
		if( !( validate( cantRightOpt, answers ))){
			TriviaException e = new TriviaException();
			throw e.repeatException();
		}
		
		correct = new Integer[cantOptions];
		optaux = new String[options.size()];
		int i = 0;
		for( String opt : options){
			if( answers.contains(opt)){
				correct[i] = new Integer(1);
				correctQtty++;
			} else {
				correct[i] = new Integer(0);
			}
			i++;
		}
		
		// Si no hay respuestas correctas de entre las opciones posibles...
		if (correctQtty == 0) {
			TriviaException e = new TriviaException();
			throw e.rightOpException();
		}
			
		if (parser.hasNextLine()) {
			line = parser.nextLine();
			score = Integer.valueOf(line);
		}
		else
			score = -1;
		
		if( score < 0 ){
			TriviaException e = new TriviaException();
			throw e.valueException();
		}
		
		((MultipleQuestion)question).setCorrect(correct);
		((MultipleQuestion)question).setOptions((String[])options.toArray(optaux));
		((MultipleQuestion)question).setLevel(difficulty);
		((MultipleQuestion)question).setScore(score);
		((MultipleQuestion)question).setCorrectCount(cantRightOpt);
		
		return;		
	}
	
	private boolean validate( Integer num, Set< String> options ){
		

		for( int i = 0; i < num; i++ ){
				if (parser.hasNextLine()) {
					line = parser.nextLine();
					line = line.trim(); 
				} else {
					return false;
				}
				
				if( options.add(line) == false ){
					return false;
				}
			}
			
			
			return true;			
	}
	
	private void parserBooleanQuestion(Question question ) throws TriviaException{
		Integer score;
		
			for( int i = 0; i < cantOptions; i++){
				if (parser.hasNextLine()) {
					line = parser.nextLine();
				} else {
					line = "";
				}
				
				if( !( (line.equalsIgnoreCase("verdadero") ) || ( line.equalsIgnoreCase("falso") ) ) ){
					TriviaException e = new TriviaException();
					throw e.notBooleanException();				
				}
			}
		
			if (parser.hasNextLine()) {
				line = parser.nextLine();
			} else {
				line = "";
			}
			if( !( (line.equalsIgnoreCase("verdadero") ) || ( line.equalsIgnoreCase("falso") ) ) ){
				TriviaException e = new TriviaException();
				throw e.notBooleanException();
			}
			else{
				if (line.equalsIgnoreCase("verdadero")) {
					((BooleanQuestion)question).setAnswer(new Boolean(true));
				} else {
					((BooleanQuestion)question).setAnswer(new Boolean(false));
				}
			
			((BooleanQuestion)question).setLevel(difficulty);
			}
		
			if (parser.hasNextLine()) {
				line = parser.nextLine();
				score = Integer.valueOf(line);
			}
			else
				score = -1;
			
			if( score < 0 ){
				TriviaException e = new TriviaException();
				throw e.valueException();
			}
			
			((BooleanQuestion)question).setScore(score);
	}
	
	/**
	 * Metodo para cerra el archivo
	 */
	private void closeFile() throws IOException{
		
		if( parser != null ){
			parser.close();
		}
		aFile.close();
	}
	
	
	@Override
	protected void finalize() throws Throwable {
		super.finalize();
		closeFile();
		
	}
		
}
